<?php
/**
 * Created by PhpStorm.
 * User: ari
 * Date: 12/14/2014
 * Time: 1:51 PM
 */
namespace BC\Wallet\Bitcoin;

use BC\SiteIndex;
use BC\User\Grant\AbstractGrant;
use BC\User\Service\TestUser;
use BC\Wallet\WalletLedger;
use CPath\Build\IBuildable;
use CPath\Build\IBuildRequest;
use CPath\Data\Map\IKeyMapper;
use CPath\Render\HTML\Attribute\Attributes;
use CPath\Render\HTML\Attribute\StyleAttributes;
use CPath\Render\HTML\Element\Form\HTMLCheckBoxField;
use CPath\Render\HTML\Element\Form\HTMLForm;
use CPath\Render\HTML\Element\Form\HTMLInputField;
use CPath\Render\HTML\Element\Form\HTMLPasswordField;
use CPath\Render\HTML\Element\Form\HTMLRangeInputField;
use CPath\Render\HTML\Element\Form\HTMLSelectField;
use CPath\Render\HTML\Element\Form\HTMLSubmit;
use CPath\Render\HTML\Element\Form\HTMLTextAreaField;
use CPath\Render\HTML\Element\HTMLElement;
use CPath\Render\HTML\Header\HTMLHeaderScript;
use CPath\Render\HTML\HTMLContainer;
use CPath\Render\Map\MapRenderer;
use CPath\Request\Exceptions\RequestException;
use CPath\Request\Executable\ExecutableRenderer;
use CPath\Request\Executable\IExecutable;
use CPath\Request\Form\IFormRequest;
use CPath\Request\IRequest;
use CPath\Request\Session\ISessionRequest;
use CPath\Request\Validation\RequiredValidation;
use CPath\Response\Common\RedirectResponse;
use CPath\Response\Common\RenderableResponse;
use CPath\Response\IResponse;
use CPath\Response\Response;
use CPath\Response\ResponseRenderer;
use CPath\Route\IRoutable;
use CPath\Route\RouteBuilder;
use CPath\UnitTest\ITestable;
use CPath\UnitTest\IUnitTestRequest;

class BitcoinWallet extends AbstractGrant implements ITestable, IExecutable, IRoutable, IBuildable
{
	const KEYRING_NAME = '.pubring.wallet.gpg';

	const FORM_PATH = '/wallet/';
	const FORM_METHOD = 'POST';

	const COMMAND_AUTHORIZE = 'authorize';
	const COMMAND_DEAUTHORIZE = 'deauthorize';
	const COMMAND_TRANSFER = 'transfer';
	const COMMAND_REVERSE = 'reverse';
	const COMMAND_SETTLE = 'settle';
	const COMMAND_UPDATE = 'update';
	const COMMAND_VIEW = 'view';

	private static $CommandText = array(
		"Select a command" => null,
		"View Ledger" => self::COMMAND_VIEW,
		"Verify (tumble password)" => self::COMMAND_TUMBLE,
		"Transfer funds" => self::COMMAND_TRANSFER,
		"Authorize funds" => self::COMMAND_AUTHORIZE,
		"Reverse funds" => self::COMMAND_REVERSE,
		"Settle funds" => self::COMMAND_SETTLE,
		"De-Authorize user" => self::COMMAND_DEAUTHORIZE,
		"Update Block-chain Info" => self::COMMAND_UPDATE,
		"Delete wallet" => self::COMMAND_DELETE,
	);

	const PARAM_AUTH_AMOUNT = 'auth-amount';
	const PARAM_TRUSTEE = 'trustee';
	const PARAM_WALLET = 'wallet';
	const PARAM_YES = 'yes';

	const ADDRESS_PREFIX = 'U';
	const DEFAULT_NAME = "Bitcoin Wallet";

	const JSON_ADDRESS = 'address';

	const JSON_PASSPHRASE_COMMENTS = '{
	"#comments": [
		"/**",
		" * This is the contents of your decrypted wallet file.",
		" * If you are reading this, that means you successfully decrypted",
		" * your wallet file and validated your authority to create a transaction.",
		" */"]
}';

	const KEY_LEDGER = ':ledger';
	const JSON_LEDGER = 'ledger';
	const JSON_LEDGER_COMMENTS = '{
	"#comments": [
		"/**",
		" * This is the contents of your wallet ledger.",
		" * If you are reading this, that means you successfully decrypted",
		" * your wallet ledger and may authorize payment actions against it.",
		" */"]
}';

	/** @var WalletLedger */
	private $mLedger = null;

	private $mSetCommand = null;
	private $mAuthLimits = null;
	private $mSetTrustee = null;
	/** @var \Closure */
	private $mSuccessCallback = null;

	public function __construct($grantFingerprint) {
		parent::__construct($grantFingerprint);
	}

	public function setCommand($command) {
		if(!in_array($command, self::$CommandText))
			throw new \InvalidArgumentException("Command not found: " . $command);
		$this->mSetCommand = $command;
		return $this;
	}

	public function setTrustee($trustee) {
		$this->mSetTrustee = $trustee;
		return $this;
	}

	public function setAuthorizeLimits($min, $max, $default, $step=null) {
		$this->mAuthLimits = func_get_args();
		return $this;
	}

	public function setSuccessCallback(\Closure $callback) {
		$this->mSuccessCallback = $callback;
		return $this;
	}

	/**
	 * @param $address
	 * @return BitcoinAddressLookup
	 * @throws \Exception
	 */
	public function getAddressLookup($address) {
		$Table = new BitcoinAddressLookupTable;
		return $Table->fetch($Table::COLUMN_ADDRESS, $address)
			?: $Table->insertAndFetch(array(
				$Table::COLUMN_ADDRESS => $address,
				$Table::COLUMN_LAST_CHECK => 0,
			));
	}

	public function checkBalance(IRequest $Request, $passphrase=null, $force=false) {
		$recipients = array();
		$json = $this->decryptGrantContents($Request, static::KEY_PUBLIC, $passphrase, $recipients);
		$address = $json[self::JSON_ADDRESS];

		$Lookup = $this->getAddressLookup($address);
		$Lookup->updateBlockChainInfo($Request, $force);
		$balance = $Lookup->getBalance();

		return $balance;
	}

	protected function formatJSONContents($content_key, Array &$json = null) {
		parent::formatJSONContents($content_key, $json);

		switch($content_key) {
			case static::KEY_PUBLIC:
				$this->checkJSON($json, self::JSON_ADDRESS);
				$json = json_decode(static::JSON_PUBLIC_COMMENTS, true) + $json;
				break;

			case static::KEY_PRIVATE:
				$this->checkJSON($json, self::JSON_PRIVATE_KEY);
				$json = json_decode(static::JSON_PRIVATE_COMMENTS, true) + $json;
				break;

			case static::KEY_LEDGER:
				$this->checkJSON($json, self::JSON_LEDGER);
				$json = json_decode(static::JSON_LEDGER_COMMENTS, true) + $json;
				break;

			default:
		}
	}

	public function decryptLedger(IRequest $Request, $passphrase=null, &$recipients=null) {
		$json = $this->decryptGrantContents($Request, static::KEY_LEDGER, $passphrase, $recipients);
		$this->mLedger = new WalletLedger($json[static::JSON_LEDGER]);
		return $this->mLedger;
	}


	/**
	 * Execute a command and return a response. Does not render
	 * @param IRequest $Request
	 * @throws RequestException
	 * @throws \CPath\Request\Validation\Exceptions\ValidationException
	 * @return IResponse the execution response
	 */
	function execute(IRequest $Request) {
		$SessionRequest = $Request;
		if(!$SessionRequest instanceof ISessionRequest)
			throw new RequestException("No session");
//		if(!UserSession::hasSession($SessionRequest))
//			throw new RequestException("No user session");

		$requestPath = self::getRequestPath($this->getGrantFingerprint());
		$FormCommand = new HTMLForm(self::FORM_METHOD, $requestPath, static::FORM_NAME,
			$InputFingerprint = new HTMLInputField(self::PARAM_FINGERPRINT, $this->getGrantFingerprint(), 'hidden'),
			new MapRenderer($this),

			new HTMLElement('fieldset', 'fieldset-wallet',
				new HTMLElement('legend', 'legend-grant', $this->getPGPInfo($Request)->getUserID()),

				/** @var HTMLSelectField $FieldCommand */
				$FieldCommand = new HTMLSelectField(self::PARAM_COMMAND, 'field-' . self::PARAM_COMMAND, self::$CommandText,
					new Attributes('onchange', 'document.location.href = jQuery(this.form).attr("action") + "/" + jQuery(this).val()')
				)
//
//				"\nSubmit command\n",
//				$Input['field-submit'] = new HTMLSubmit('Submit')
			)
		);


		$command = null;
		if(isset($Request[self::PARAM_COMMAND]))
			$command = $Request[self::PARAM_COMMAND];
		if(!$command && $this->mSetCommand)
			$command = $this->mSetCommand;

		if(!$command)
			return $FormCommand;

		$commandText = array_search($command, self::$CommandText);

		$FormPost = new HTMLForm('POST', $requestPath, static::FORM_NAME,

			new HTMLHeaderScript(AbstractGrant::DIR . '\assets\form-grant.js'),

			new HTMLInputField(self::PARAM_FINGERPRINT, $this->getGrantFingerprint(), 'hidden'),
			new HTMLInputField(self::PARAM_COMMAND, $command, 'hidden'),
//			new MapRenderer($this),

			$CommandContainer = new HTMLElement('fieldset', 'fieldset-grant-options',
				new HTMLElement('legend', 'legend-grant-options', "Options")

			),

			new HTMLElement('fieldset', 'fieldset-grant',
				new HTMLElement('legend', 'legend-grant', $commandText . ' - ' . $this->getPGPInfo($Request)->getUserID()),

				new HTMLSubmit('submit', ucwords($command))
			),

			new HTMLElement('fieldset', 'fieldset-challenge toggle',
//				new StyleAttributes('display', 'none'),


				new HTMLElement('fieldset', 'fieldset-passphrase',
					new StyleAttributes('display', 'none'),
					new HTMLElement('legend', 'legend-passphrase', "Please enter PGP Passphrase"),

					new HTMLPasswordField(self::PARAM_PASSPHRASE,
						new Attributes('disabled', 'disabled')
					)
				),

				new HTMLElement('legend', 'legend-challenge', "Grant Challenge"),

				$FieldChallenge = new HTMLTextAreaField(self::PARAM_CHALLENGE, $this->getPassphraseChallengeContent(true)),
				new HTMLInputField(self::PARAM_CHALLENGE, '', 'hidden'),
				"<br/><br/>Grant Answer<br/>",
				$FieldChallengePassphrase = new HTMLInputField(self::PARAM_CHALLENGE_PASSPHRASE,
					new RequiredValidation()
				)
			)
		);

		switch ($command) {
			case static::COMMAND_AUTHORIZE:
				list($min, $max, $default, $step) = $this->mAuthLimits ?: array(null, null, null, null);
				$CommandContainer->addAll(
					"Enter trustee's fingerprint to authorize<br/>",
					$InputTrustee = new HTMLInputField(self::PARAM_TRUSTEE, $this->mSetTrustee),
					"<br/><br/>Enter authorization amount<br/>",
					new HTMLInputField(self::PARAM_AUTH_AMOUNT, $default, null, 'field-' . self::PARAM_AUTH_AMOUNT,
						new RequiredValidation(),
//						function(IRequest $Request, $value) { if(!$value || $value == 0) throw new \InvalidArgumentException("Invalid Amount"); },
						new Attributes('length', 8)
					),
					$InputAmount = new HTMLRangeInputField(null, $default, $min, $max, $step,
						new Attributes('onmousemove', "jQuery(this).prevAll('input:first').val(this.value)")
					)
				);
				break;

			case static::COMMAND_DEAUTHORIZE:
				$CommandContainer->addAll(
					"Enter trustee's fingerprint to de-authorize<br/>",
					$InputTrustee = new HTMLInputField(self::PARAM_TRUSTEE)
				);
				break;

			case static::COMMAND_TRANSFER:
				$CommandContainer->addAll(
					"<br/><br/>Enter authorized trustee<br/>",
					$InputTrustee = new HTMLInputField(self::PARAM_TRUSTEE, $this->mSetTrustee,
						new RequiredValidation()
					),
					"<br/><br/>Enter authorized amount to transfer<br/>",
					$InputAmount = new HTMLInputField(self::PARAM_AUTH_AMOUNT,
						new RequiredValidation()
					),
					"<br/><br/>Enter wallet id to transfer to<br/>",
					$InputWalletID = new HTMLInputField(self::PARAM_WALLET,
						new RequiredValidation()
					)
				);
				break;

			case static::COMMAND_REVERSE:
				$CommandContainer->addAll(
					"Enter authorized trustee<br/>",
					$InputTrustee = new HTMLInputField(self::PARAM_TRUSTEE, $this->mSetTrustee),
					"<br/><br/>Enter transferred amount to reverse<br/>",
					$InputAmount = new HTMLInputField(self::PARAM_AUTH_AMOUNT, new RequiredValidation()),
					"<br/><br/>Enter wallet id to transfer to<br/>",
					$InputWalletID = new HTMLInputField(self::PARAM_WALLET)
				);
				break;

			case static::COMMAND_SETTLE:
				$CommandContainer->addAll(
					"Enter wallet id to settle with<br/>",
					$InputWalletID = new HTMLInputField(self::PARAM_WALLET)
				);
				break;

			case static::COMMAND_DELETE:
				$CommandContainer->addAll(
					"Are you super sure you want to delete this choice wallet bra ?it betta be empty<br/>",
					new HTMLInputField(self::PARAM_YES, 0, 'hidden'),
					$CheckYes = new HTMLCheckBoxField(self::PARAM_YES, false, 1)
				);
				break;

			case static::COMMAND_VIEW:
				break;

			case static::COMMAND_TUMBLE:
			case static::COMMAND_UPDATE:
				break;

			default:
				throw new RequestException("Invalid Wallet Command: " . $command);
		}

//		$FormPost->setFormValues($Request);

		if(!$Request instanceof IFormRequest)
			return $FormPost;

		if(!$commandText) {
			throw new \InvalidArgumentException("Invalid command: " . $commandText); }

		$FormPost->validateRequest($Request);

		$passphrase = $FormPost->validateField($Request, self::PARAM_CHALLENGE_PASSPHRASE);
		try {
			$Response = $this->executeGrantAction($Request, $passphrase);
			return new ResponseRenderer($Response);
		} catch (IResponse $ex) {
			return new HTMLContainer(
				new ResponseRenderer($ex),
				$FormPost
			);
		}
//
//		return new HTMLContainer(
//			new ResponseRenderer($Response),
//			$FormPost
//		);
	}

	/**
	 * Execute the granted action with the current passphrase
	 * @param IRequest $Request
	 * @param string $passphrase
	 * @throws RequestException
	 * @throws \BC\User\Grant\Exceptions\MissingGrantContentException
	 * @throws \Exception
	 * @return \CPath\Response\IResponse
	 */
	protected function executeGrantAction(IRequest $Request, $passphrase) {
		$SessionRequest = $Request;
		if(!$SessionRequest instanceof ISessionRequest)
			throw new RequestException("Request does not implement ISessionRequest");

		$command = $Request[self::PARAM_COMMAND];
		$call = $this->mSuccessCallback;



		switch($command) {
			case static::COMMAND_TUMBLE:
				$this->tumblePassphrase($Request, $passphrase);
				$Response = new Response("Grant passphrase tumbled successfully");
				break;

			case static::COMMAND_AUTHORIZE:
				$trustee = $this->mSetTrustee ?: $Request[self::PARAM_TRUSTEE];
				$amount = $Request[self::PARAM_AUTH_AMOUNT];

				$recipients = array();

				$jsonLedger = $this->decryptGrantContents($Request, static::KEY_LEDGER, $passphrase, $recipients);
				$Ledger = new WalletLedger($jsonLedger[static::JSON_LEDGER]);

				$this->getAddressLookup($Ledger->getWalletAddress())
					->updateBlockChainInfo($Request);

				$this->tumblePassphrase($Request, $passphrase);
				$Ledger->authorize($amount, $trustee);

				$this->formatJSONContents(static::KEY_LEDGER, $jsonLedger);
				$this->writeGrantContent($Request, static::KEY_LEDGER, $jsonLedger, $recipients, $passphrase);

				$this->grantToUser($Request, $trustee, $passphrase);

				if($call) {
					$call($Request, $command, $amount, $trustee);
					$call = null;
				}

				$Response = new Response("Amount has been authorized: " . $amount);
				break;

			case static::COMMAND_DEAUTHORIZE:
				$trustee = $this->mSetTrustee ?: $Request[self::PARAM_TRUSTEE];

				$recipients = array();
				$jsonLedger = $this->decryptGrantContents($Request, static::KEY_LEDGER, $passphrase, $recipients);
				$Ledger = new WalletLedger($jsonLedger[static::JSON_LEDGER]);

				$this->getAddressLookup($Ledger->getWalletAddress())
					->updateBlockChainInfo($Request);

				$this->tumblePassphrase($Request, $passphrase);
				$Ledger->deauthorize($trustee);

				$this->formatJSONContents(static::KEY_LEDGER, $jsonLedger);
				$this->writeGrantContent($Request, static::KEY_LEDGER, $jsonLedger, $recipients, $passphrase);

				$this->unGrantUser($Request, $trustee, $passphrase);

				$Response = new Response("Trustee has been de-authorized: " . $trustee);
				break;

			case static::COMMAND_TRANSFER:
				$trustee = $this->mSetTrustee ?: $Request[self::PARAM_TRUSTEE];
				$amount = $Request[self::PARAM_AUTH_AMOUNT];
				$walletID = $Request[self::PARAM_WALLET];

				$recipients = array();
				$jsonLedger = $this->decryptGrantContents($Request, static::KEY_LEDGER, $passphrase, $recipients);
				$Ledger = new WalletLedger($jsonLedger[static::JSON_LEDGER]);

				$this->getAddressLookup($Ledger->getWalletAddress())
					->updateBlockChainInfo($Request);

				$this->tumblePassphrase($Request, $passphrase);
				$Ledger->transfer($amount, $walletID, $trustee);

				$this->formatJSONContents(static::KEY_LEDGER, $jsonLedger);
				$this->writeGrantContent($Request, static::KEY_LEDGER, $jsonLedger, $recipients, $passphrase);

				//$this->grantToUser($Request, $walletFingerprint, $passphrase);

				$Response = new Response("Amount has been transferred: " . $amount);
				break;


			case static::COMMAND_REVERSE:
				$trustee = $this->mSetTrustee ?: $Request[self::PARAM_TRUSTEE];
				$amount = $Request[self::PARAM_AUTH_AMOUNT];
				$walletID = $Request[self::PARAM_WALLET];

				$recipients = array();
				$jsonLedger = $this->decryptGrantContents($Request, static::KEY_LEDGER, $passphrase, $recipients);
				$Ledger = new WalletLedger($jsonLedger[static::JSON_LEDGER]);

				$this->tumblePassphrase($Request, $passphrase);
				$Ledger->reverse($amount, $walletID, $trustee);

				$this->formatJSONContents(static::KEY_LEDGER, $jsonLedger);
				$this->writeGrantContent($Request, static::KEY_LEDGER, $jsonLedger, $recipients, $passphrase);

				if($Ledger->getTransferBalanceForWallet($walletID) === 0)
					$this->unGrantUser($Request, $walletID, $passphrase);

				$Response = new Response("Transfer amount has been reversed: " . $amount);
				break;


			case static::COMMAND_SETTLE:
				$walletID = $Request[self::PARAM_WALLET];

				$recipients = array();
				$jsonLedger = $this->decryptGrantContents($Request, static::KEY_LEDGER, $passphrase, $recipients);
				$Ledger = new WalletLedger($jsonLedger[static::JSON_LEDGER]);

				$amount = $Ledger->getTransferBalanceForWallet($walletID);
				$this->tumblePassphrase($Request, $passphrase);
				$Ledger->settle($walletID);

				$this->formatJSONContents(static::KEY_LEDGER, $jsonLedger);
				$this->writeGrantContent($Request, static::KEY_LEDGER, $jsonLedger, $recipients, $passphrase);

				if($Ledger->getTransferBalanceForWallet($walletID) === 0)
					$this->unGrantUser($Request, $walletID, $passphrase);

				$Response = new Response("Transfer amount has been settled: " . $amount);
				break;

			case static::COMMAND_UPDATE:
				$recipients = array();
				$jsonLedger = $this->decryptGrantContents($Request, static::KEY_LEDGER, $passphrase, $recipients);
				$Ledger = new WalletLedger($jsonLedger[static::JSON_LEDGER]);

				$this->verifyPassphrase($Request, $passphrase);

				$address = $Ledger->getWalletAddress();
				$Lookup = $this->getAddressLookup($address);
				$Lookup->updateBlockChainInfo($Request);

				$updated = $Ledger->updateBalance($Lookup->getBalance(), $Lookup->getReceivedByAddress(), $Lookup->getSentByAddress());

				$this->formatJSONContents(static::KEY_LEDGER, $jsonLedger);
				$this->writeGrantContent($Request, static::KEY_LEDGER, $jsonLedger, $recipients, $passphrase);

				$Response = $updated
					? new Response("Block chain info updated successfully")
					: new Response("No update");
				break;

			case static::COMMAND_VIEW:
				$this->verifyPassphrase($Request, $passphrase);
				$jsonLedger = $this->decryptGrantContents($Request, static::KEY_LEDGER, $passphrase, $recipients);
				$Ledger = new WalletLedger($jsonLedger[static::JSON_LEDGER]);
				$Response = new RenderableResponse("Ledger decrypted successfully");
				$Response->addContent(new MapRenderer($Ledger));
				break;

			case static::COMMAND_DELETE:
				if(!$Request[self::PARAM_YES])
					throw new RequestException("Checkbox must be checked to say 'yes', but were still deciding, weren't you");

				$this->verifyPassphrase($Request, $passphrase);

				$jsonLedger = $this->decryptGrantContents($Request, static::KEY_LEDGER, $passphrase, $recipients);
				new WalletLedger($jsonLedger[static::JSON_LEDGER]);

				$json = $this->decryptGrantContents($Request, self::KEY_PRIVATE, $passphrase);
				$this->checkJSON($json, static::JSON_PRIVATE_KEY);

				$this::delete($Request, $this, $passphrase);
				$Response = new Response(get_class($this) . " deleted successfully");
				break;


			default:
				throw new RequestException("Invalid Wallet Command: " . $command);
		}

		if($call)
			$call($Request, $command);

		return $Response;
	}

	function mapKeys(IKeyMapper $Map) {
		parent::mapKeys($Map);
		if($this->mLedger)
			$this->mLedger->mapKeys($Map);
	}


	// Static


	static function getRequestPath($walletID=null) {
		return '/wallet/' . $walletID;
	}

	/**
	 * @param $userFingerprints
	 * @param null $className
	 * @return BitcoinWallet[]
	 */
	public static function search($userFingerprints, $className=null) {
		return parent::search($userFingerprints, $className);
	}

	public static function create(IRequest $Request, $recipients, $grantID=null, &$newPassphrase = null, $comment = null) {
		$VanityGen = new VanityGenCommand();
		$VanityGen->execute($Request, static::ADDRESS_PREFIX);
		$address = $VanityGen->getGeneratedAddress();
		$privateKey = $VanityGen->getGeneratedPrivateKey();
		/** @var BitcoinWallet $Wallet */
		$Wallet = parent::create($Request, $recipients, $grantID, $newPassphrase, $address);

		$json = array();
		$json[self::JSON_PRIVATE_KEY] = $privateKey;
		$Wallet->formatJSONContents(static::KEY_PRIVATE, $json);
		$Wallet->writeGrantContent($Request, static::KEY_PRIVATE, $json, $recipients, $newPassphrase);

		$json = array();
		$json[self::JSON_ADDRESS] = $address;
		$Wallet->formatJSONContents(static::KEY_PUBLIC, $json);
		$Wallet->writeGrantContent($Request, static::KEY_PUBLIC, $json, $recipients, $newPassphrase);

		$Lookup = $Wallet->getAddressLookup($address);
		$Lookup->updateLastCheck();

		$json = array();
		$json[self::JSON_LEDGER] = array();

		$Ledger = new WalletLedger($json[self::JSON_LEDGER]);
		$Ledger->initLedger($address, $recipients);
		$Ledger->updateBalance(1500, 2000, 500);

		$Wallet->formatJSONContents(static::KEY_LEDGER, $json);
		$Wallet->writeGrantContent($Request, static::KEY_LEDGER, $json, $recipients, $newPassphrase);


		$Wallet->verifyPassphrase($Request, $newPassphrase);
		return $Wallet;
	}

	protected static function delete(IRequest $Request, BitcoinWallet $Wallet, $passphrase) {
		$json = $Wallet->decryptGrantContents($Request, $Wallet::KEY_PUBLIC, $passphrase);
		$address = $Wallet->checkJSON($json, $Wallet::JSON_ADDRESS);
		static::deleteGrant($Request, $Wallet, $passphrase);

		$Table = new BitcoinAddressLookupTable;
		$Table->delete($Table::COLUMN_ADDRESS, $address)
			->execDelete($Request, true);
	}

	/**
	 * Route the request to this class object and return the object
	 * @param IRequest $Request the IRequest inst for this render
	 * @param array|null $Previous all previous response object that were passed from a handler, if any
	 * @param null|mixed $_arg [varargs] passed by route map
	 * @return void|bool|Object returns a response object
	 * If nothing is returned (or bool[true]), it is assumed that rendering has occurred and the request ends
	 * If false is returned, this static handler will be called again if another handler returns an object
	 * If an object is returned, it is passed along to the next handler
	 */
	static function routeRequestStatic(IRequest $Request, Array &$Previous = array(), $_arg = null) {
		$fingerprint = $Request[self::PARAM_FINGERPRINT];
		$Wallet = new BitcoinWallet($fingerprint);
		return new ExecutableRenderer(
			$Wallet,
			true
		);
	}

	/**
	 * Handle this request and render any content
	 * @param IBuildRequest $Request the build request inst for this build session
	 * @return void
	 * @build --disable 0
	 * Note: Use doctag 'build' with '--disable 1' to have this IBuildable class skipped during a build
	 */
	static function handleBuildStatic(IBuildRequest $Request) {
		$Builder = new RouteBuilder($Request, new SiteIndex());
		$Builder->writeRoute('ANY ' . self::FORM_PATH . ':' . self::PARAM_FINGERPRINT, __CLASS__);
		$Builder->writeRoute('ANY ' . self::FORM_PATH . ':' . self::PARAM_FINGERPRINT . ':' . self::PARAM_COMMAND, __CLASS__);
	}

	/**
	 * Perform a unit test
	 * @param IUnitTestRequest $Test the unit test request inst for this test session
	 * @return void
	 * @test --disable 0
	 * Note: Use doctag 'test' with '--disable 1' to have this ITestable class skipped during a build
	 */
	static function handleStaticUnitTest(IUnitTestRequest $Test) {
		$mockAddresses = array();
		$Test->addMock(BitcoinAddressLookup::MOCK_UPDATE,
		function($Request, Array $addresses) use (&$mockAddresses) {
			$json = array(
				'addresses' => array()
			);
			foreach($addresses as $address) {
				if(!in_array($address, $mockAddresses))
					continue;
				$crc = abs(crc32($address));
				$balance = $crc % 3500 + 1000;
				$received = $crc % 350000;
				$json['addresses'][] = array(
					"address" => $address,
					"n_tx" => $crc % 3530,
					"total_received" => $received,
					"total_sent" => $received - $balance,
					"final_balance" => $balance
				);
			}
			return json_encode($json);
		});

		$TestUser = new TestUser($Test, 'bit-tester', 'bitpass');

		$Wallets = BitcoinWallet::search($TestUser->getFingerprint());
		$passphrase = null;
		$Wallet = isset($Wallets[0]) ? array_shift($Wallets)
			: BitcoinWallet::create($Test, $TestUser->getFingerprint(), 'test-wallet', $passphrase);
		$passphrase = $TestUser->solveChallengePassphrase($Test, $Wallet);
		$address = $Wallet->decryptLedger($Test, $passphrase)
			->getWalletAddress();
		$mockAddresses[] = $address;


		$Test->setRequestParameter(static::PARAM_COMMAND, static::COMMAND_UPDATE);
		$Test->setRequestParameter(static::PARAM_CHALLENGE_PASSPHRASE, $passphrase);
		$Wallet->execute($Test);

		$balance = $Wallet->checkBalance($Test, $passphrase);
		$Test->assert(is_int($balance));
		$Test->assertEqual($balance, $Wallet->decryptLedger($Test, $passphrase)->getAvailableBalance());

		$Wallet2 = isset($Wallets[0]) ? array_shift($Wallets)
			: BitcoinWallet::create($Test, $TestUser->getFingerprint(), 'test-wallet2');
		$passphrase2 = $TestUser->solveChallengePassphrase($Test, $Wallet2);
		$address2 = $Wallet2->decryptLedger($Test, $passphrase2)
			->getWalletAddress();
		$mockAddresses[] = $address2;

		$Test->setRequestParameter(static::PARAM_COMMAND, static::COMMAND_UPDATE);
		$Test->setRequestParameter(static::PARAM_CHALLENGE_PASSPHRASE, $passphrase2);
		$Wallet2->execute($Test);

		$trustee = $Wallet2->getGrantFingerprint();

		$authorize = 1000;
		$passphrase = $TestUser->solveChallengePassphrase($Test, $Wallet);
		$Test->setRequestParameter(static::PARAM_COMMAND, static::COMMAND_AUTHORIZE);
		$Test->setRequestParameter(static::PARAM_AUTH_AMOUNT, $authorize);
		$Test->setRequestParameter(static::PARAM_TRUSTEE, $trustee);
		$Test->setRequestParameter(static::PARAM_CHALLENGE_PASSPHRASE, $passphrase);
		$Wallet->execute($Test);

		$transfer = $authorize/2;
		$passphrase = $TestUser->solveChallengePassphrase($Test, $Wallet);
		$Test->setRequestParameter(static::PARAM_COMMAND, static::COMMAND_TRANSFER);
		$Test->setRequestParameter(static::PARAM_AUTH_AMOUNT, $transfer);
		$Test->setRequestParameter(static::PARAM_WALLET, $Wallet2->getGrantFingerprint());
		$Test->setRequestParameter(static::PARAM_TRUSTEE, $trustee);
		$Test->setRequestParameter(static::PARAM_CHALLENGE_PASSPHRASE, $passphrase);
		$Wallet->execute($Test);

		$reverse = $transfer;
		$passphrase = $TestUser->solveChallengePassphrase($Test, $Wallet);
		$Test->setRequestParameter(static::PARAM_COMMAND, static::COMMAND_REVERSE);
		$Test->setRequestParameter(static::PARAM_AUTH_AMOUNT, $reverse);
		$Test->setRequestParameter(static::PARAM_WALLET, $Wallet2->getGrantFingerprint());
		$Test->setRequestParameter(static::PARAM_TRUSTEE, $trustee);
		$Test->setRequestParameter(static::PARAM_CHALLENGE_PASSPHRASE, $passphrase);
		$Wallet->execute($Test);

		$passphrase = $TestUser->solveChallengePassphrase($Test, $Wallet);
		$Test->setRequestParameter(static::PARAM_COMMAND, static::COMMAND_DEAUTHORIZE);
		$Test->setRequestParameter(static::PARAM_TRUSTEE, $trustee);
		$Test->setRequestParameter(static::PARAM_CHALLENGE_PASSPHRASE, $passphrase);
		$Wallet->execute($Test);

//
//		$passphrase = $TestUser->solveGrantPassphrase($Test, $Wallet);
//		$Test->setRequestParameter(static::REQUEST_COMMAND, static::COMMAND_DELETE);
//		$Wallet->executeGrantAction($Test, $passphrase);
//
//		$passphrase2 = $TestUser->solveGrantPassphrase($Test, $Wallet2);
//		$Test->setRequestParameter(static::REQUEST_COMMAND, static::COMMAND_DELETE);
//		$Wallet2->executeGrantAction($Test, $passphrase2);
	}

}


